﻿//
// Copyright (c) 2011 HyperCrunch, Inc. http://www.hypercrunch.com 
// Author: Nik Kalyani  http://www.kalyani.com (Twitter: @techbubble)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions 
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
//

using System;
using System.Reflection;
using System.Web;
using System.Collections.Generic;
using System.IO;
using System.Web.Script.Serialization;
using DotNetNuke.Security;
using DotNetNuke.Entities.Modules;


namespace HyperCrunch.Druid.Mobile
{
    public class AppControllerBase
    {
        ///<summary>
        /// Instantiates a handler dynamically and passes values to parameters
        /// Context items are matched to parameters and values passed for int, bool, DateTime and string types
        ///</summary>
        public ActionResult HandleRequest(ModuleInfo moduleInfo, UIContext uiContext)
        {
            HttpContext context = uiContext.Context;
            JavaScriptSerializer serializer = new JavaScriptSerializer();

            // The "route" querystring variable contains the method to call
            // along with some parameters in the format /Method/{id}
            // We ignore all but the first parameter and assume it is "id"
            string path = context.Request.QueryString[Globals.MOBILE_ROUTE_PARAMETER];
            string[] pathFrags = path.Substring(1).Split('/');

            // We are going to build a collection of parameters based on values submitted with the request
            Dictionary<string, object> handlerParams = new Dictionary<string, object>();
            // 
            // 1) Get the "id" from the path
            if (pathFrags.Length > 1) handlerParams.Add("id", pathFrags[1]);

            // 2) If JSON data has been passed, we capture and deserialize it and add non-duplicate values to the collection
            Dictionary<string, object> data = (Dictionary<string,object>)serializer.DeserializeObject(GetJsonRequest(context.Request));
            if (data != null)
            {
                foreach (string field in data.Keys)
                {
                    if (field == null) continue;
                    if (!handlerParams.ContainsKey(field))
                        handlerParams.Add(field.ToLower(), data[field]);
                }
            }

            // 3) We capture any form values and add them to the collection as well
            if (context.Request.Form.HasKeys())
            {
                foreach (string field in context.Request.Form.Keys)
                {
                    if (field == null) continue;
                    if (!handlerParams.ContainsKey(field))
                        handlerParams.Add(field.ToLower(), context.Request.Form[field]);
                }
            }

            // 4) We capture any querystring values and add them to the collection as well
            if (context.Request.QueryString.HasKeys())
            {
                foreach (string field in context.Request.QueryString.Keys)
                {
                    if (field == null) continue;
                    if (!handlerParams.ContainsKey(field))
                        handlerParams.Add(field.ToLower(), context.Request.QueryString[field]);
                }
            }

            ActionResult result = null;
            try
            {
                // Get a list of all methods that match the requested method name and RequestMethod
                List<MethodInfo> handlerMethods = GetMatchingMethods(pathFrags[0], (HttpVerbType)Enum.Parse(typeof(HttpVerbType), context.Request.HttpMethod));

                // Find the method that best matches the parameter values we currently have
                MethodInfo handlerMethod = GetBestMethodMatch(handlerMethods, handlerParams);

                // Build an array of parameter values that can be passed to the method during invocation
                object[] requestParams = GetMethodParameterValues(moduleInfo, uiContext, handlerMethod, handlerParams);

                result = (ActionResult)handlerMethod.Invoke(this, requestParams);
            }
            catch (Exception exc)
            {
                result = new ErrorResult { Content = exc.Message, ErrorCode = 500 };
            }

            return result;
        }

        /// <summary>
        /// Matches up parameter values present in handlerParams with parameter names for all parameters
        /// of handlerMethod and returns an array of object values suitable for passing to the Invoke()
        /// method of handlerMethod
        /// </summary>
        /// <param name="uiContext"></param>
        /// <param name="serializer"></param>
        /// <param name="handlerMethod"></param>
        /// <param name="handlerParams"></param>
        /// <returns></returns>
        private object[] GetMethodParameterValues(ModuleInfo moduleInfo, UIContext uiContext, MethodInfo handlerMethod, Dictionary<string, object> handlerParams)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer(); 
            ParameterInfo[] methodParams = handlerMethod.GetParameters();
            object[] requestParams = new object[methodParams.Length];
            for (int p = 0; p < methodParams.Length; p++)
            {
                string paramName = methodParams[p].Name.ToLower();
                Type paramType = methodParams[p].ParameterType;
                try
                {
                    if (paramType.Name.ToLower() == "uicontext")
                        requestParams[p] = uiContext;
                    else if (paramType.Name.ToLower() == "moduleinfo")
                        requestParams[p] = moduleInfo;
                    else if (handlerParams.ContainsKey(paramName))
                        requestParams[p] = serializer.ConvertToType(handlerParams[paramName], paramType);
                }
                catch
                {
                    requestParams[p] = null;
                }
            }

            return requestParams;
        }

        private List<MethodInfo> GetMatchingMethods(string requestMethod, HttpVerbType requestVerb)
        {
            List<MethodInfo> handlerMethods = new List<MethodInfo>();
            string method = requestMethod.ToLower();

            // Find a controller method that matches what is being requested
            Type type = this.GetType();
            MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
            if (methods == null)
                throw new Exception("Controller does not have any methods that can be invoked");

            for (int m = 0; m < methods.Length; m++)
            {
                if (methods[m].Name.ToLower() == method)
                {
                    if ((methods[m].ReturnType.Name == "ActionResult") || (methods[m].ReturnType.BaseType.Name == "ActionResult"))
                    {
                        RequestMethodAttribute[] attributes = (RequestMethodAttribute[])methods[m].GetCustomAttributes(typeof(RequestMethodAttribute), false);
                        foreach (RequestMethodAttribute attribute in attributes)
                        {
                            if (attribute.Verb == requestVerb)
                            {
                                handlerMethods.Add(methods[m]);
                                break;
                            }
                        }
                    }
                }
            }

            // If there are no matching methods to the one requested, we can't do anything
            if (handlerMethods.Count == 0)
                throw new Exception("Controller does not have any methods named \"" + requestMethod + "\"");

            return handlerMethods;
        }

        private string GetJsonRequest(HttpRequest request)
        {
            string rawJson = String.Empty;
            if ((request.ContentType.Contains("application/json")) && (request.ContentLength > 0))
            {
                request.InputStream.Position = 0;
                using (StreamReader reader = new StreamReader(request.InputStream))
                {
                    rawJson = reader.ReadToEnd();
                }
            }

            return rawJson;
        }

        private MethodInfo GetBestMethodMatch(List<MethodInfo> handlerMethods, Dictionary<string, object> handlerParams)
        {
            MethodInfo bestMethod = null;
            int maxParamMatch = 0;
            foreach (MethodInfo method in handlerMethods)
            {
                int currentParamMatch = 0;
                ParameterInfo[] parameterInfo = method.GetParameters();
                for (int p = 0; p < parameterInfo.Length; p++)
                {
                    if (handlerParams.ContainsKey(parameterInfo[p].Name.ToLower()))
                        currentParamMatch++;
                }

                if (currentParamMatch > maxParamMatch)
                {
                    bestMethod = method;
                    maxParamMatch = currentParamMatch;
                }
            }

            if (bestMethod == null)
                bestMethod = handlerMethods[0];

            return bestMethod;
        }

    }
}
